/**
 * 
 */
package edu.fiu.cis.selfhealingdisk;

import java.util.Collections;

import edu.fiu.cis.selfhealingdisk.IORequest.IOEnum;

/**
 * @author vanvorst
 * 
 */
public class FragmentedParityModel extends ParityModel {

	public static int calcExtraBlocksPerGroup(long totalParityGroups,
			long parityGroupSize) {
		return 0;
	}

	protected FragmentedParityModel(IOTrace originalTrace,
			DiskLayout originalLayout) {
		super(originalTrace, originalLayout);
	}

	private BlockedUpdates dataBlockedEvents = new BlockedUpdates();
	private BlockedUpdates parityBlockedEvents = new BlockedUpdates();


	@Override
	public Event nextEvent(long currentTime) {

		IORequest or = null;
		IORequest tr = null;
		IORequestList requests = new IORequestList();

		// Check if there is not event pending in any queue
		if (((or = this.originalTrace.peek()) == null)
				&& this.parityBlockedEvents.isEmpty()
				&& this.dataBlockedEvents.isEmpty()) {
			return Event.createDoneEvent();
		}

		// Return all the pending write request that were blocked in parity
		// reads
		IORequestList unblocked = null;
		unblocked = this.parityBlockedEvents.getRequests(currentTime);
		for (IORequest r : unblocked) {
			r.setRequestTime(currentTime);
			requests.add(r);
		}

		// Return all data events that were blocked for a parity write
		unblocked = this.dataBlockedEvents.getRequests(currentTime);
		for (IORequest r : unblocked) {
			r.setRequestTime(currentTime);
			addDataWriteRequest(r,requests);
		}
	
		// Get all trace events before the t[i+1] time
		while ((or = this.originalTrace.peek()) != null
				&& or.getRequestTime() <= currentTime) {
			// Move the iterator
			this.originalTrace.next();

			// Create the new trace entry using the old entry
			tr = new IORequest(or.getRequestTime(), this.originalLayout
					.calcPhysicalBlock(or.getLba()), or.getSize(), or.getType(),false);

			if (or.getType().equals(IOEnum.READ)) {
				// All read requests are sent as soon as possible to the disk
				requests.add(tr);
			} else {
				if (safe(tr)) {
					addDataWriteRequest(tr,requests);
				}
			}

		}

		
		for (IORequest ior: requests) {
			this.transformedTrace.addIORequest(ior);
		}
		
		if (!requests.isEmpty()) {
			Collections.sort(requests);
			// Case II: We have request for this window. Sent requests in time
			// [ti,ti+1]
			return Event.createIOEvent(requests);
		} else {
			if (or != null && this.dataBlockedEvents.isEmpty()
					&& this.parityBlockedEvents.isEmpty()) {
				// Case III: No Pending requests in time [ti,ti+1] and not
				// waiting events
				// We can warp to the next request
				//System.out.println("WARP from:" + currentTime  + "to" + or.getRequestTime());
				return Event.createWarpEvent(or.getRequestTime());
			}
			// Case IV: No Pending requests in time [ti,ti+1] and some events
			// are waiting. Need to wait for disk answer
			//System.out.println("(" + currentTime + "BLOCKED:" + this.dataBlockedEvents.size() + " " + this.parityBlockedEvents.size() );
			return Event.createIOEvent(requests);
		}
	}
	
	public void addDataWriteRequest(IORequest tr,IORequestList requests) {
		requests.add(tr);

		// Sent the parity read request
		long parityBlock = this.originalLayout.calcParityBlock(tr.getLba());
		
		//Force the read for this parity
		IORequest pr = new IORequest(tr.getRequestTime() + 1,parityBlock,tr.getSize(), IOEnum.READ,true);
		requests.add(pr);

		// Put at outstanding event in the parity waiting queue
		IORequest wr = new IORequest(-1,parityBlock, tr.getSize(), IOEnum.WRITE,true);
		this.parityBlockedEvents.setExpectedRequest(parityBlock,pr);
		this.parityBlockedEvents.blockRequest(parityBlock, wr);
		this.dataBlockedEvents.setExpectedRequest(parityBlock, wr);
	}
	

	/** 
	 * Check if the writing request is safe. To be safe the request must conform with two conditions:
	 * 1) There is not previous data block, waiting to update the same parity
	 * 2) The last parity block change is commited, this is there is not writing pending in the block 
	 * @param tr the writing request
	 * @return
	 */
	public boolean safe(IORequest tr) {
		long parityBlock = this.originalLayout.calcParityBlock(tr.getLba());
		
		// Exists events waiting for the same parity block. Add the event to the queue
		if (this.dataBlockedEvents.pendingEventsForParityUnit(parityBlock)) {
			tr.setRequestTime(-1);
			this.dataBlockedEvents.blockRequest(parityBlock, tr);
			return false;
		}
		
		
		
		return true;
	}

}
